package socket2;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * 聊天室服务端
 */
public class Server {
    /*
     * java.net.ServerSocket
     * 运行在服务端, 主要作用有两个
     * 1.向系统申请服务端口, 客户端可以通过改端口与服务端建立连接
     * 2.监听服务端口, 一旦一个客户端连接了, 就会立即建立一个Socket对象, 并且返回
     */
    private ServerSocket serverSocket;//总机

    private List<PrintWriter> allOut = new ArrayList<>();//设计集合存放所有客户端输出流, 用于群发

    public Server(){
        try{
            /*
             * 创建一个ServerSocket对象时, 会申请一个端口
             * 如果该端口被其他程序占用, 则会抛出异常:
             * java.net.BindException: Address already in use
             * 解决办法:
             *  1.杀死占用该端口的进程()
             *  2.更换端口号
             */
            System.out.println("正在启动服务器");
            serverSocket = new ServerSocket(8088);
            System.out.println("服务器启动完毕");
        }catch(IOException e){
            e.printStackTrace();
        }
    }

    /**
     * 该方法用于启动服务端
     */
    public void start(){
        try {
            while(true){
                System.out.println("等待客户连接...");
                /*
                 * ServerSocket提供的重要方法
                 * Socket accept(): 用于接受客户端的连接, 并返回一个Socket实例与连接的客户端交互,该方法是一个阻塞王法, 调用后程序会"卡主", 知道一个客户端连接为止
                 */
                Socket socket = serverSocket.accept();
                System.out.println("一个客户连接了!");
                //启动一个线程来处理该客户端的交互
                ClientHandler handler = new ClientHandler(socket);
                Thread t = new Thread(handler);
                t.start();
            }
        } catch (IOException e) {
            //throw new RuntimeException(e);//客户端强制终止时(关闭, 拔网线)会报错, 可以将catch内的方法体删除, 不捕获异常, 冷处理
        }
    }
    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

    /**
     * 该线程任务用于负责与指定的客户端交互
     */
    private class ClientHandler implements Runnable{
        private Socket socket;
        public ClientHandler(Socket socket){
            this.socket = socket;
        }
        public void run(){
            try {
                //通过socket可以获取到网络输入流, 读取客户端发送过来的消息
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr);
                /*
                 * 当我们通过br.readLine()方法读取对方法son过来的一行字符串时,
                 * 远端计算机如果调用scoket.close()正常与我们断开连接(进行四次挥手)
                 * 那么readLine()方法会返回null
                 * 若对方是异常中断(强制杀死进程),此时readLine()会抛出异常
                 * 这种情况服务端是没有办法的
                 */
                /*
                // 接受一条消息
                //String message = br.readLine();//读取一行来字远端计算机发送过来的字符串
                //System.out.println("客户端说: " + message);
                 */

                //通过socket获取输出流用于向客户端发送消息
                OutputStream out = socket.getOutputStream();
                OutputStreamWriter osw = new OutputStreamWriter(out,StandardCharsets.UTF_8);
                BufferedWriter bw = new BufferedWriter(osw);
                PrintWriter pw = new PrintWriter(bw,true);

                allOut.add(pw); //将客户端输出流存入共享群聊集合中

                String message;
                while((message=br.readLine())!=null){//读取客户端发送过来的多条消息
                    sendMessageToAll("客户端说: "+message);
                }
            } catch (IOException e) {
            }finally{
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        /**
         * 将消息发送给所有客户端(群聊)
         */
        private void sendMessageToAll(String message){
            System.out.println(message);
            for(PrintWriter o : allOut){
                o.println(message);
            }
        }
    }
}
